﻿using System.Globalization;
using System.Linq.Dynamic.Core;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;

namespace Devonline.AspNetCore;

public static class AspNetCoreExtensions
{
    #region host & service extensions
    /// <summary>
    /// 使用 TAppSetting 类型获取配置文件 AppSetting 节点配置项, 并以单例形式添加到服务容器并返回
    /// </summary>
    /// <typeparam name="TAppSetting">AppSetting 配置项的类型名称</typeparam>
    /// <param name="builder">WebApplicationBuilder 实例</param>
    /// <returns></returns>
    public static TAppSetting UseAppSetting<TAppSetting>(this WebApplicationBuilder builder) where TAppSetting : class, new()
    {
        var appSetting = builder.Configuration.GetSetting<TAppSetting>();
        builder.Services.AddSingleton(appSetting);
        return appSetting;
    }
    /// <summary>
    /// 使用 TSetting 类型获取配置文件 setting 节点配置项, 并以单例形式添加到服务容器并返回
    /// 若不传递 setting 则会使用 TSetting 类型同名的配置节点
    /// 若不能转换配置项, 则返回 TSetting 的新实例
    /// </summary>
    /// <typeparam name="TSetting">setting 或 TSetting 类型同名配置项的类型名称</typeparam>
    /// <param name="builder">WebApplicationBuilder 实例</param>
    /// <returns></returns>
    public static TSetting UseSetting<TSetting>(this WebApplicationBuilder builder, string? setting = null) where TSetting : class, new()
    {
        setting ??= typeof(TSetting).Name;
        var settingInstance = builder.Configuration.GetSetting<TSetting>(setting) ?? new TSetting();
        builder.Services.AddSingleton(settingInstance);
        return settingInstance;
    }
    /// <summary>
    /// 注册默认的基于字符串主键的实体数据对象模型数据操作相关服务
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddDataServices(this IServiceCollection services)
    {
        services.AddTransient(typeof(IServiceBase<>), typeof(ServiceBase<>));
        services.AddTransient(typeof(IDataService<,>), typeof(DataService<,>));
        services.AddTransient(typeof(IAttachmentService<,,>), typeof(AttachmentService<>));

        services.AddTransient(typeof(IDataWithAttachmentService<,>), typeof(DataWithAttachmentService<,>));
        services.AddTransient(typeof(IDataWithAdditionalService<,,>), typeof(DataWithAdditionalService<,,>));
        services.AddTransient(typeof(IDataWithCollectionService<,,>), typeof(DataWithCollectionService<,,>));
        services.AddTransient(typeof(IDataWithAdditionalAndAttachmentService<,,>), typeof(DataWithAdditionalAndAttachmentService<,,>));

        services.AddTransient(typeof(IExcelExportService), typeof(ExcelExportService));
        services.AddTransient(typeof(IExcelImportService<,>), typeof(ExcelImportService<>));
        services.AddTransient(typeof(IExcelService<,>), typeof(ExcelService<>));

        return services;
    }
    #endregion

    #region mvc & api extensions
    /// <summary>
    /// 用于依赖注入的 json 全局设置
    /// 为避免客户端字段名首字母大小写的问题, 将不再使用 CamelCase
    /// </summary>
    /// <param name="builder"></param>
    /// <returns></returns>
    public static IMvcBuilder AddJsonOptions(this IMvcBuilder builder)
    {
        builder.AddJsonOptions(option =>
        {
            option.JsonSerializerOptions.AllowTrailingCommas = true;
            option.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
            option.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
            option.JsonSerializerOptions.IgnoreReadOnlyFields = false;
            option.JsonSerializerOptions.IgnoreReadOnlyProperties = false;
            option.JsonSerializerOptions.IncludeFields = false;
            option.JsonSerializerOptions.MaxDepth = AppSettings.UNIT_FOUR;
            option.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals;
            option.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
            option.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
            option.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip;
            option.JsonSerializerOptions.WriteIndented = true;

            // 枚举使用字符串表示形式
            option.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
            //// 时间格式使用自定义
            //option.JsonSerializerOptions.Converters.Add(new DateTimeConverter(AppSettings.DEFAULT_DATETIME_FORMAT));
            //option.JsonSerializerOptions.Converters.Add(new DateTimeOffsetConverter(AppSettings.DEFAULT_DATETIME_FORMAT));
        });

        return builder.AddNewtonsoftJson(option =>
        {
            //使用驼峰样式的key
            option.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            //忽略循环引用
            option.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            option.SerializerSettings.TypeNameHandling = TypeNameHandling.None;
            option.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
            //设置时间格式
            option.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
            option.SerializerSettings.DateParseHandling = DateParseHandling.DateTime;
            option.SerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
            //option.SerializerSettings.DateFormatString = AppSettings.DEFAULT_DATETIME_FORMAT;

            //枚举类型转字符串输出
            option.SerializerSettings.Converters.Add(new StringEnumConverter());
        });
    }
    #endregion

    #region http request extensions
    /// <summary>
    /// 获取查询选项, 将来自 HttpPost 的 OData 查询选项加入到 QueryString 键值对中
    /// </summary>
    /// <param name="request">WebApi 的 HttpRequest 对象</param>
    /// <returns>返回合并后的查询选项</returns>
    public static IEnumerable<KeyValuePair<string, StringValues>> GetRequestOptions(this HttpRequest request)
    {
        var queryString = request.Query.ToList();
        if (request.HasFormContentType && (request.Form.Keys?.Count ?? 0) > 0)
        {
            queryString.AddRange(request.Form);
        }

        if (request.Headers.IsNotNullOrEmpty())
        {
            queryString.AddRange(request.Headers);
        }

        if (request.Cookies.IsNotNullOrEmpty())
        {
            queryString.AddRange(request.Cookies.ToDictionary(x => x.Key, x => new StringValues(x.Value)));
        }

        return queryString;
    }
    /// <summary>
    /// 从当前 request 中获取参数的值, 取值顺序 Query -> Form -> Header -> Cookie
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="request"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    public static T? GetRequestOption<T>(this HttpRequest request, string key) where T : IConvertible
    {
        var value = string.Empty;
        if (request.Query.Keys.Contains(key))
        {
            value = request.Query[key];
        }
        else if (request.HasFormContentType && request.Form.Keys.IsNotNullOrEmpty() && request.Form.Keys.Contains(key))
        {
            value = request.Form[key];
        }
        else if (request.Headers.IsNotNullOrEmpty() && request.Headers.ContainsKey(key))
        {
            value = request.Headers[key];
        }
        else if (request.Cookies.IsNotNullOrEmpty() && request.Cookies.Keys.Contains(key))
        {
            value = request.Cookies[key];
        }

        if (!string.IsNullOrWhiteSpace(value))
        {
            return (T)Convert.ChangeType(value, typeof(T), CultureInfo.CurrentCulture);
        }

        return default;
    }
    /// <summary>
    /// 获取请求的完整 uri 字符串
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public static Uri GetAbsoluteUri(this HttpRequest request) => new(new StringBuilder()
         .Append(request.Scheme)
         .Append("://")
         .Append(request.Host)
         .Append(request.PathBase)
         .Append(request.Path)
         .Append(request.QueryString)
         .ToString());
    /// <summary>
    /// 通过 request query 获取 QueryOptions 的实例
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="request"></param>
    /// <returns></returns>
    public static QueryOptions<T> GetQueryOptions<T>(this HttpRequest request) => new(new QueryOptionRawValue(request.Query));
    #endregion

    #region http context extensions
    /// <summary>
    /// 获取上下文对象中参数/变量的值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="httpContext"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    public static T? GetContextOption<T>(this HttpContext httpContext, string key) where T : IConvertible
    {
        var value = httpContext.User.Claims.FirstOrDefault(c => c.Type == key)?.Value;
        return !string.IsNullOrWhiteSpace(value) ? value.To<T>() : httpContext.Request.GetRequestOption<T>(key);
    }
    /// <summary>
    /// 从 httpContext 获取用户标识, 用户标识在 User.Claims 中的 type 为 sub; 在 request 中为 userId
    /// </summary>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    public static string? GetUserId(this HttpContext httpContext)
    {
        var userId = httpContext.User.Claims.FirstOrDefault(c => c.Type == AppSettings.CLAIM_TYPE_USER_ID)?.Value;
        userId ??= httpContext.User.Claims.FirstOrDefault(c => c.Type == AppSettings.CLAIM_TYPE_JWT_SUBJECT)?.Value;
        return userId ?? httpContext.Request.GetRequestOption<string>(nameof(userId));
    }
    #endregion
}